﻿using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using IcicleFramework.Components;
using IcicleFramework.Components.Renderable;
using IcicleFramework.Entities;
using IcicleFramework.Renderables;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using TiledLib;

namespace IcicleFramework.GameServices
{
    public class GameObjectFactory : GameService, IGameObjectFactory
    {
        public event GameObjectCreatedHandler OnGameObjectCreated;

        private ContentManager content;

        private XDocument xmlDoc;

        private string pathToXML;

        private string fullXMLPath;

        public IComponentFactory ComponentFactory { get; set; }
        
        public string PathToXML
        {
            get { return pathToXML; }
            set
            {
                pathToXML = value;

                fullXMLPath = Path.Combine(content.RootDirectory, pathToXML);
                xmlDoc = XDocument.Load(fullXMLPath);
            }
        }

        public GameObjectFactory(ContentManager content)
        {
            this.content = content;
        }

        public override void Initialize()
        {
            this.ComponentFactory = (IComponentFactory) GameServiceManager.GetService(typeof (IComponentFactory));
        }

        public void GenerateComponents(IGameObject gameObject, XElement element)
        {
            if (element.Element("components") != null && ComponentFactory != null)
            {
                List<XElement> componentElements = element.Element("components").Elements("component").ToList();

                //Run through all the components defined in the XElement and create them.
                for (int i = 0; i < componentElements.Count; i++)
                {
                    //Instantiate the component itself.
                    IBaseComponent component = ComponentFactory.GenerateComponent(componentElements[i], gameObject);

                    //Finally, add the component to the provided IGameObject.
                    if (component != null && component.BaseType != null)
                        gameObject.AddComponent(component.BaseType, component);
                }
            }
        }
        
        public IGameObject GenerateGameObjectFromName(string objectName)
        {
            IGameObject gameObject = null;
            
            if (xmlDoc != null)
            {
                gameObject = new GameObject();

                //Find the element corresponding to the object name, if it exists...
                if (xmlDoc.Root != null)
                {
                    List<XElement> elementList = (xmlDoc.Root.Elements("entity")
                        .Where(el => (string) el.Attribute("name") == objectName)).ToList();

                    //Just grab the first element if there was one.
                    if (elementList.Count > 0)
                        GenerateComponents(gameObject, elementList[0]);
                }
            }

            if (gameObject != null)
            {
                gameObject.Position = new Vector2(float.NegativeInfinity, float.NegativeInfinity);
                gameObject.HasMoved = false;

                if (OnGameObjectCreated != null)
                    OnGameObjectCreated(gameObject);

                //Now that the object and all of its components have been created, and the subsystems have been notified,
                //run the 'burn in' phase by 'Initializing' them.
                gameObject.Initialize();
                gameObject.Active = true;
            }

            return gameObject;
        }

        public IGameObject GenerateGameObjectFromName(string objectName, Vector2 position)
        {
            IGameObject gameObject = null;

            if (xmlDoc != null)
            {
                gameObject = new GameObject();
                gameObject.Position = position;

                //Find the element corresponding to the object name, if it exists...
                if (xmlDoc.Root != null)
                {
                    List<XElement> elementList = (xmlDoc.Root.Elements("entity")
                        .Where(el => (string)el.Attribute("name") == objectName)).ToList();

                    //Just grab the first element if there was one.
                    if (elementList.Count > 0)
                        GenerateComponents(gameObject, elementList[0]);
                }
            }

            if (gameObject != null)
            {
                gameObject.HasMoved = false;

                if (OnGameObjectCreated != null)
                    OnGameObjectCreated(gameObject);

                //Now that the object and all of its components have been created, and the subsystems have been notified,
                //run the 'burn in' phase by 'Initializing' them.
                gameObject.Initialize();
                gameObject.Active = true;
            }

            return gameObject;
        }
        
        #region Tiled

        /// <summary>
        /// Just a quick and dirty method for generating any object in our Tiled map that has a corresponding entity definition
        /// in an XML file.
        /// </summary>
        public List<IGameObject> GenerateXMLEnvironmentObjectsFromTiled(Map map, TileLayer layer)
        {
            List<IGameObject> gameObjects =  new List<IGameObject>();

            for (int i = 0; i < layer.Width; i++)
            {
                for (int j = 0; j < layer.Height; j++)
                {
                    if (layer.Tiles[i,j] != null)
                    {
                        IGameObject gameObject = new GameObject();
                        gameObject.Position = new Vector2(map.TileWidth * i, map.TileHeight * j);

                        //Load in the correct element from the entity definitions file.
                        string objectName = layer.Tiles[i, j].Properties["name"].RawValue;

                        if (xmlDoc.Root != null)
                        {
                            List<XElement> elements = (xmlDoc.Root.Elements("entity")
                                .Where(el => (string)el.Attribute("name") == objectName)).ToList();

                            //We'll let the IRenderableComponent build itself but before finishing it off we'll swap the texture, so just
                            //generate all the components via the Tiled file...
                            if (elements.Count > 0)
                                GenerateComponents(gameObject, elements[0]);
                        }

                        //And then swap the texture with the one from Tiled...
                        IRenderComponent renderComponent = gameObject.GetComponent<IRenderComponent>();

                        if (renderComponent != null)
                        {
                            ITextured renderable = (ITextured) renderComponent.GetRenderable(0);

                            if (renderable != null)
                            {
                                renderable.AssetName = layer.Tiles[i, j].Properties["texture"].RawValue;
                                renderComponent.Load(content);
                                renderComponent.SetLayer(1f);
                            }
                        }

                        gameObject.Initialize();
                        gameObject.Active = true;

                        if (OnGameObjectCreated != null)
                            OnGameObjectCreated(gameObject);
                    }
                }
            }

            return gameObjects;
        }

        #endregion
    }
}
